package com.viroyal.monitorlib.network.socket;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;

import com.hwangjr.rxbus.RxBus;
import com.suntiago.baseui.utils.log.Slog;
import com.viroyal.monitorlib.utils.SPUtils;

import java.util.concurrent.TimeUnit;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;

import static android.content.Context.CONNECTIVITY_SERVICE;

/**
 * 负责维护tcp长连接的类
 * <p>
 * Created by cxy on 2015/10/21.
 */
public class ClientSocket {
    private static final String TAG = ClientSocket.class.getSimpleName();

    private static final int RETRY_CONNECT_TIME = 10; //SECONDS
    private static final int READ_TIMEOUT = 20; //SECONDS

    private EventLoopGroup workerGroup = new NioEventLoopGroup();

    private ClientHandler mHandler;

    private DeviceIdEncoder mDevIdEncoder;

    private CustomMessageEncoder mCustomMessageEncoder;

    private ByteArrayEncoder mByteEncoder;

    private Channel mChannel;

    private Context mContext;

    private DeviceConnectStatus mConnectStatus;

    private String mServerIp;

    private int mServerPort;

    //自动重连标志
    private boolean mAutoReconnect;

    private Subscription mReconectSub;

    public ClientSocket(Context context) {
        mHandler = new ClientHandler(this);
        mDevIdEncoder = new DeviceIdEncoder();
        mCustomMessageEncoder = new CustomMessageEncoder();
        mByteEncoder = new ByteArrayEncoder();
        mContext = context;
        mConnectStatus = new DeviceConnectStatus();

        //register network state listener
        BroadcastReceiver connectionReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                ConnectivityManager connectMgr = (ConnectivityManager) context.getSystemService(CONNECTIVITY_SERVICE);
                NetworkInfo active = connectMgr.getActiveNetworkInfo();
                if (active != null && active.isConnected()) {
                    Slog.e(TAG, "network connected");
                    tryReconnect(false, 0);
                }
            }
        };
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(connectionReceiver, intentFilter);
    }

    public synchronized void connect(String serverIp, int serverPort) {
        mAutoReconnect = true;

        mServerIp = serverIp;
        mServerPort = serverPort;

        connect();
    }

    private synchronized void connect() {
        if (!mConnectStatus.isIdle()) {
            Slog.e(TAG, "in connection, can't call connect, status=" + mConnectStatus);
            return;
        }
        if (!isNetworkConnected(mContext)) {
            Slog.e(TAG, "no network, delay to network availale");
            //发现有一种情况，在网络信号很差的情况下，socket连接会断掉，会跑到这里重新连接
            //但是发现没有网络，于是等待网络连接的intent，可以奇怪的是并没有等到，导致
            //一直没有去自动重连，所以先在这里打一个补丁.
            tryReconnect(true, RETRY_CONNECT_TIME);
            return;
        }

        Slog.e(TAG, "start connect to:(" + mServerIp + "," + mServerPort + ")");
        mConnectStatus.setDeviceStatus(DeviceConnectStatus.DEV_CONNECTING);

        try {
            //建立socket通道
            Bootstrap b = new Bootstrap(); // (1)
            b.group(workerGroup); // (2)
            b.channel(NioSocketChannel.class); // (3)
            b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(READ_TIMEOUT, 0, 0));
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));//in
                    ch.pipeline().addLast("decoder", mHandler);//in
                    ch.pipeline().addLast(new LengthFieldPrepender(2));//out
                    ch.pipeline().addLast(mDevIdEncoder);//out
                    ch.pipeline().addLast(mCustomMessageEncoder); //out
                    ch.pipeline().addLast(mByteEncoder);
                }
            });

            // Start the client.
                ChannelFuture f = b.connect(mServerIp, mServerPort);
                f.addListener((ChannelFutureListener) future -> {
                    Slog.e(TAG, "operationComplete result=" + future.isSuccess() + ", cause=" + future.toString());
                    if (future.isSuccess()) {
                        fireStatus(DeviceConnectStatus.DEV_CONNECTED, future.channel());
                    } else {
                        mConnectStatus.setDeviceStatus(DeviceConnectStatus.DEV_DISCONNECTED);
                        tryReconnect(true, RETRY_CONNECT_TIME);
                    }
                });// (4)
        } catch (Exception e) {
            Slog.e(TAG, e.getMessage()+"Socket连接通道建立失败");
            workerGroup.shutdownGracefully();
            fireStatus(DeviceConnectStatus.DEV_DISCONNECTED, null);
        }
    }

    public synchronized void disconnect() {
        mAutoReconnect = false;
        if (mReconectSub != null) {
            mReconectSub.unsubscribe();
        }
        if (!mConnectStatus.canDisconnect()) {
            Slog.e(TAG, "idle, no need disconnect");
            return;
        }

        mConnectStatus.setDeviceStatus(DeviceConnectStatus.DEV_DISCONNECTING);
        mChannel.close().addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                fireStatus(DeviceConnectStatus.DEV_DISCONNECTED, null);
            }
        });
    }


    /**
     * 设置心跳超时时间
     *
     * @param timeSeconds 秒
     */
    public synchronized void setPingTime(final int timeSeconds) {
        if (mChannel != null) {
            try {
                mChannel.pipeline().remove("idleStateHandler");
            } catch (Exception e) {
                Slog.e(TAG, e.getMessage());
            }
            mChannel.pipeline().addFirst("idleStateHandler", new IdleStateHandler(timeSeconds, 0, 0));
        }
    }

//    public void shutdown() {
//        if (workerGroup != null)
//            workerGroup.shutdownGracefully();
//    }

    public synchronized void sendData(String payload) {
        sendData(null, payload);
    }

    public synchronized void sendData(String payload, long msgId) {
        String topic = "/rrpc/" + mDevIdEncoder.getDevId() + "/rsp/" + msgId;
        sendData(topic, payload);
    }

    public synchronized void sendData(String topic, String payload) {
        Slog.d(TAG, "send data:" + payload + "  topic=" + topic);
        if (mChannel == null) {
            Slog.e(TAG, "sendData error, channel is null");
            return;
        }
        Device2CloudMessage msg = new Device2CloudMessage();
        msg.topic =topic;
        msg.payload = payload;

        mChannel.writeAndFlush(msg);
    }

    public void setDevId(String id) {
        Slog.d(TAG, "device name:" + id);
        mDevIdEncoder.setDevId(id);
    }

    /**
     * 发送连接状态的消息
     *
     * @param statusCode status code
     * @param channel    new channel
     */
    private synchronized void fireStatus(final int statusCode, Channel channel) {
        mConnectStatus.setDeviceStatus(statusCode);
        mChannel = channel;

        DeviceConnectStatus connectStatus = new DeviceConnectStatus(statusCode);
        Slog.e(TAG, "fire status:" + connectStatus);
        SPUtils.getInstance(mContext.getApplicationContext()).put("connect_status",connectStatus.toString());
        RxBus.get().post(connectStatus);
    }

    /**
     * 关闭socket并尝试重连
     */
    synchronized void closeAndRetry() {
        if (!mConnectStatus.canDisconnect()) {
            Slog.e(TAG, "closeAndRetry can't close, status=" + mConnectStatus);
            return;
        }

        if (mChannel != null) {
            Slog.e(TAG, "close socket, mChannel=" + mChannel);
            mConnectStatus.setDeviceStatus(DeviceConnectStatus.DEV_DISCONNECTING);
            mChannel.close().addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    fireStatus(DeviceConnectStatus.DEV_DISCONNECTED, null);
                    tryReconnect(true, RETRY_CONNECT_TIME);
                }
            });
        } else {
            Slog.e(TAG, "closeAndRetry channel is null");
        }
    }

    /**
     * 自动重连
     *
     * @param delayFirst 在重连之前是否先延时一段时间，保证socket已释放完全。
     * @param delayTime  延时时间 单位秒
     */
    private synchronized void tryReconnect(boolean delayFirst, int delayTime) {
        if (!mAutoReconnect) {
            Slog.e(TAG, "tryReconnect, mAutoReconnect is false, do nothing");
            return;
        }
        if (!mConnectStatus.isIdle()) {
            Slog.e(TAG, "tryReconnect, not idle, status=" + mConnectStatus);
            return;
        }

        //延时后再连接
        if (delayFirst) {
            mConnectStatus.setDeviceStatus(DeviceConnectStatus.DEV_WAIT_RECONNECT);
            mReconectSub = Observable.timer(delayTime, TimeUnit.SECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Action1<Long>() {
                        @Override
                        public void call(Long aLong) {
                            Slog.e(TAG, "reconnect timeout, call connect");
                            mConnectStatus.setDeviceStatus(DeviceConnectStatus.DEV_DISCONNECTED);
                            connect();
                        }
                    });
        } else {
            connect();
        }
    }

    private boolean isNetworkConnected(Context context) {
        ConnectivityManager connectivity = (ConnectivityManager) context
                .getSystemService(CONNECTIVITY_SERVICE);
        if (null != connectivity) {
            NetworkInfo info = connectivity.getActiveNetworkInfo();
            if (null != info && info.isConnected()) {
                if (info.getState() == NetworkInfo.State.CONNECTED) {
                    return true;
                }
            }
        }
        return false;
    }
}
